package chexuan.game.interfaces.io.connection;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

import chexuan.game.bean.packet.common.Packet;

public abstract class AbstractSocketConnection {

	public static final int INVALID_CONNECTION_ID = -1;
	public static final int INITIALIZE_CONNECTION_ID = 0;
	private final static AtomicInteger connectionCounter = new AtomicInteger(
			INITIALIZE_CONNECTION_ID);

	private final static Set<ConnectionCreationListener> connectionEstablishedListeners = new CopyOnWriteArraySet<ConnectionCreationListener>();

	public static boolean DEBUG_ENABLED = false;

	static {
		try {
			DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
		} catch (Exception e) {
			// Ignore.
		}
	}

	protected final Collection<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>();

	protected final Collection<PacketCollector> collectors = new ConcurrentLinkedQueue<PacketCollector>();

	protected final Map<PacketListener, ListenerWrapper> recvListeners = new ConcurrentHashMap<PacketListener, ListenerWrapper>();

	protected final Map<PacketListener, ListenerWrapper> sendListeners = new ConcurrentHashMap<PacketListener, ListenerWrapper>();

	protected final Map<PacketInterceptor, InterceptorWrapper> interceptors = new ConcurrentHashMap<PacketInterceptor, InterceptorWrapper>();

	protected final int connectionCounterValue = connectionCounter
			.getAndIncrement();

	public abstract int getConnectionId();

	public abstract boolean connect() throws IOException;

	public abstract void disconnect() throws IOException;

	public abstract boolean isConnected();

	public abstract boolean isAuthenticated();

	public abstract boolean authenticate();

	public abstract void sendPacket(Packet packet) throws IOException;

	public static void addConnectionCreationListener(
			ConnectionCreationListener connectionCreationListener) {
		connectionEstablishedListeners.add(connectionCreationListener);
	}

	public static void removeConnectionCreationListener(
			ConnectionCreationListener connectionCreationListener) {
		connectionEstablishedListeners.remove(connectionCreationListener);
	}

	protected static Collection<ConnectionCreationListener> getConnectionCreationListeners() {
		return Collections
				.unmodifiableCollection(connectionEstablishedListeners);
	}

	public void addConnectionListener(ConnectionListener connectionListener) {
		if (!isConnected()) {
			throw new IllegalStateException("Not connected to server.");
		}
		if (connectionListener == null) {
			return;
		}
		if (!connectionListeners.contains(connectionListener)) {
			connectionListeners.add(connectionListener);
		}
	}

	public void removeConnectionListener(ConnectionListener connectionListener) {
		connectionListeners.remove(connectionListener);
	}

	protected Collection<ConnectionListener> getConnectionListeners() {
		return connectionListeners;
	}

	public PacketCollector createPacketCollector(PacketFilter packetFilter) {
		PacketCollector collector = new PacketCollector(this, packetFilter);
		// Add the collector to the list of active collectors.
		collectors.add(collector);
		return collector;
	}

	protected void removePacketCollector(PacketCollector collector) {
		collectors.remove(collector);
	}

	protected Collection<PacketCollector> getPacketCollectors() {
		return collectors;
	}

	public void addPacketListener(PacketListener packetListener,
			PacketFilter packetFilter) {
		if (packetListener == null) {
			throw new NullPointerException("Packet listener is null.");
		}
		ListenerWrapper wrapper = new ListenerWrapper(packetListener,
				packetFilter);
		recvListeners.put(packetListener, wrapper);
	}

	public void removePacketListener(PacketListener packetListener) {
		recvListeners.remove(packetListener);
	}

	protected Map<PacketListener, ListenerWrapper> getPacketListeners() {
		return recvListeners;
	}

	public void addPacketSendingListener(PacketListener packetListener,
			PacketFilter packetFilter) {
		if (packetListener == null) {
			throw new NullPointerException("Packet listener is null.");
		}
		ListenerWrapper wrapper = new ListenerWrapper(packetListener,
				packetFilter);
		sendListeners.put(packetListener, wrapper);
	}

	public void removePacketSendingListener(PacketListener packetListener) {
		sendListeners.remove(packetListener);
	}

	protected Map<PacketListener, ListenerWrapper> getPacketSendingListeners() {
		return sendListeners;
	}

	protected void firePacketSendingListeners(Packet packet) {
		// Notify the listeners of the new sent packet
		for (ListenerWrapper listenerWrapper : sendListeners.values()) {
			listenerWrapper.notifyListener(packet);
		}
	}

	public void addPacketInterceptor(PacketInterceptor packetInterceptor,
			PacketFilter packetFilter) {
		if (packetInterceptor == null) {
			throw new NullPointerException("Packet interceptor is null.");
		}
		interceptors.put(packetInterceptor, new InterceptorWrapper(
				packetInterceptor, packetFilter));
	}

	public void removePacketInterceptor(PacketInterceptor packetInterceptor) {
		interceptors.remove(packetInterceptor);
	}

	protected Map<PacketInterceptor, InterceptorWrapper> getPacketInterceptors() {
		return interceptors;
	}

	protected void firePacketInterceptors(Packet packet) {
		if (packet != null) {
			for (InterceptorWrapper interceptorWrapper : interceptors.values()) {
				interceptorWrapper.notifyListener(packet);
			}
		}
	}

	protected static class ListenerWrapper {

		private final PacketListener packetListener;
		private final PacketFilter packetFilter;

		/**
		 * Create a class which associates a packet filter with a listener.
		 * 
		 * @param packetListener
		 *            the packet listener.
		 * @param packetFilter
		 *            the associated filter or null if it listen for all
		 *            packets.
		 */
		public ListenerWrapper(PacketListener packetListener,
				PacketFilter packetFilter) {
			this.packetListener = packetListener;
			this.packetFilter = packetFilter;
		}

		/**
		 * Notify and process the packet listener if the filter matches the
		 * packet.
		 * 
		 * @param packet
		 *            the packet which was sent or received.
		 */
		public void notifyListener(Packet packet) {
			if (packetFilter == null || packetFilter.accept(packet)) {
				packetListener.processPacket(packet);
			}
		}
	}

	/**
	 * A wrapper class to associate a packet filter with an interceptor.
	 */
	protected static class InterceptorWrapper {

		private final PacketInterceptor packetInterceptor;
		private final PacketFilter packetFilter;

		/**
		 * Create a class which associates a packet filter with an interceptor.
		 * 
		 * @param packetInterceptor
		 *            the interceptor.
		 * @param packetFilter
		 *            the associated filter or null if it intercepts all
		 *            packets.
		 */
		public InterceptorWrapper(PacketInterceptor packetInterceptor,
				PacketFilter packetFilter) {
			this.packetInterceptor = packetInterceptor;
			this.packetFilter = packetFilter;
		}

		@Override
		public boolean equals(Object object) {
			if (object == null) {
				return false;
			}
			if (object instanceof InterceptorWrapper) {
				return ((InterceptorWrapper) object).packetInterceptor
						.equals(this.packetInterceptor);
			} else if (object instanceof PacketInterceptor) {
				return object.equals(this.packetInterceptor);
			}
			return false;
		}

		/**
		 * Notify and process the packet interceptor if the filter matches the
		 * packet.
		 * 
		 * @param packet
		 *            the packet which will be sent.
		 */
		public void notifyListener(Packet packet) {
			if (packetFilter == null || packetFilter.accept(packet)) {
				packetInterceptor.interceptPacket(packet);
			}
		}
	}
}
